
import numpy as np
import numpy
import cmath
import math
import functools
from typing import List
from UliEngineering.SignalProcessing.Simulation import sine_wave
import iio
from common.third_party.devmem import DevMem, DevMemCache
import scipy.signal
from scipy.signal import filtfilt, firwin


FIRWIN_5E5_256 = [
    8.161820889789103e-05,
    7.18815759384496e-05,
    6.196861631458429e-05,
    5.178819794210575e-05,
    4.1244028350383734e-05,
    3.0235575598202085e-05,
    1.8659049053200723e-05,
    6.408434795357922e-06,
    -6.623419998336777e-06,
    -2.0543679706716553e-05,
    -3.545835631958415e-05,
    -5.147111557410417e-05,
    -6.868206013150349e-05,
    -8.718649699740841e-05,
    -0.00010707369659582431,
    -0.00012842565107441207,
    -0.00015131583954594796,
    -0.0001758080080566818,
    -0.00020195497211574447,
    -0.00022979744962024496,
    -0.0002593629319677218,
    -0.0002906646010610417,
    -0.0003237002997806668,
    -0.0003584515633257141,
    -0.00039488271860891466,
    -0.00043294005863218535,
    -0.0004725510984700093,
    -0.0005136239191483702,
    -0.0005560466053290654,
    -0.000599686782294371,
    -0.0006443912572772282,
    -0.0006899857696992488,
    -0.0007362748543652991,
    -0.000783041821121487,
    -0.0008300488539157426,
    -0.0008770372316095206,
    -0.0009237276722783881,
    -0.0009698208021114119,
    -0.0010149977493774003,
    -0.001058920863273511,
    -0.0011012345568117226,
    -0.00114156627223462,
    -0.0011795275667872657,
    -0.001214715316010069,
    -0.0012467130310620108,
    -0.0012750922859377583,
    -0.001299414249809607,
    -0.0013192313191091242,
    -0.0013340888433672764,
    -0.0013435269382588875,
    -0.0013470823787506611,
    -0.0013442905647348354,
    -0.001334687551045621,
    -0.0013178121333058087,
    -0.0012932079806388323,
    -0.001260425805909684,
    -0.0012190255638285285,
    -0.0011685786669658519,
    -0.0011086702094892477,
    -0.0010389011882411382,
    -0.0009588907106352459,
    -0.0008682781787586314,
    -0.0007667254390264566,
    -0.0006539188867489785,
    -0.0005295715150350231,
    -0.00039342489757346425,
    -0.00024525109500381773,
    -8.485447480868392e-05,
    8.792656506643314e-05,
    0.00027321797833709983,
    0.00047110856734658126,
    0.0006816486035539431,
    0.0009048485797562745,
    0.001140678101747656,
    0.001389064926565366,
    0.0016498941538823353,
    0.0019230075764764404,
    0.0022082031950446455,
    0.0025052349019364637,
    0.0028138123376595836,
    0.0031336009232643745,
    0.003464222070946634,
    0.0038052535744230614,
    0.004156230179835062,
    0.0045166443371277435,
    0.004885947131035573,
    0.005263549389988883,
    0.005648822970439228,
    0.0060411022132912506,
    0.006439685568327647,
    0.006843837381726078,
    0.007252789840996449,
    0.007665745070917312,
    0.008081877373325453,
    0.008500335602915953,
    0.008920245670545397,
    0.009340713164900885,
    0.009760826082806297,
    0.010179657657886707,
    0.01059626927680593,
    0.0110097134718324,
    0.011419036978078287,
    0.01182328384339743,
    0.012221498578621398,
    0.012612729335561755,
    0.012996031100011332,
    0.01337046888683934,
    0.013735120924195562,
    0.014089081813817718,
    0.014431465654474214,
    0.014761409115671528,
    0.015078074448911323,
    0.015380652423996374,
    0.015668365178155844,
    0.015940468966087955,
    0.016196256799400597,
    0.016435060964365984,
    0.01665625540739224,
    0.016859257978150764,
    0.01704353252088055,
    0.017208590805017396,
    0.01735399428696318,
    0.017479355695516476,
    0.017584340434225906,
    0.017668667794699656,
    0.017732111975704352,
    0.017774502903710025,
    0.01779572685138217,
    0.01779572685138217,
    0.017774502903710025,
    0.017732111975704352,
    0.017668667794699656,
    0.017584340434225906,
    0.017479355695516476,
    0.01735399428696318,
    0.017208590805017396,
    0.01704353252088055,
    0.016859257978150764,
    0.01665625540739224,
    0.016435060964365984,
    0.016196256799400597,
    0.015940468966087955,
    0.015668365178155844,
    0.015380652423996374,
    0.015078074448911325,
    0.014761409115671528,
    0.014431465654474214,
    0.014089081813817718,
    0.013735120924195562,
    0.01337046888683934,
    0.012996031100011332,
    0.012612729335561755,
    0.012221498578621398,
    0.01182328384339743,
    0.011419036978078287,
    0.0110097134718324,
    0.010596269276805933,
    0.010179657657886707,
    0.009760826082806297,
    0.009340713164900885,
    0.008920245670545397,
    0.008500335602915953,
    0.008081877373325453,
    0.007665745070917312,
    0.007252789840996449,
    0.006843837381726078,
    0.006439685568327647,
    0.0060411022132912506,
    0.005648822970439228,
    0.005263549389988885,
    0.004885947131035574,
    0.004516644337127745,
    0.004156230179835064,
    0.003805253574423061,
    0.0034642220709466334,
    0.0031336009232643745,
    0.002813812337659583,
    0.0025052349019364637,
    0.0022082031950446455,
    0.0019230075764764404,
    0.0016498941538823353,
    0.0013890649265653663,
    0.0011406781017476563,
    0.0009048485797562747,
    0.0006816486035539433,
    0.00047110856734658137,
    0.0002732179783370997,
    8.792656506643312e-05,
    -8.485447480868389e-05,
    -0.0002452510950038177,
    -0.00039342489757346425,
    -0.0005295715150350231,
    -0.0006539188867489785,
    -0.0007667254390264568,
    -0.0008682781787586315,
    -0.000958890710635246,
    -0.0010389011882411387,
    -0.0011086702094892482,
    -0.0011685786669658523,
    -0.001219025563828528,
    -0.0012604258059096837,
    -0.001293207980638832,
    -0.0013178121333058085,
    -0.001334687551045621,
    -0.0013442905647348354,
    -0.0013470823787506611,
    -0.0013435269382588882,
    -0.001334088843367277,
    -0.0013192313191091245,
    -0.0012994142498096077,
    -0.001275092285937759,
    -0.0012467130310620115,
    -0.001214715316010069,
    -0.0011795275667872657,
    -0.00114156627223462,
    -0.0011012345568117226,
    -0.001058920863273511,
    -0.0010149977493774003,
    -0.0009698208021114119,
    -0.0009237276722783881,
    -0.0008770372316095206,
    -0.0008300488539157431,
    -0.0007830418211214876,
    -0.0007362748543652994,
    -0.0006899857696992483,
    -0.0006443912572772276,
    -0.000599686782294371,
    -0.0005560466053290654,
    -0.0005136239191483702,
    -0.0004725510984700093,
    -0.00043294005863218535,
    -0.00039488271860891466,
    -0.0003584515633257141,
    -0.0003237002997806668,
    -0.00029066460106104197,
    -0.00025936293196772203,
    -0.00022979744962024518,
    -0.00020195497211574439,
    -0.0001758080080566816,
    -0.00015131583954594796,
    -0.00012842565107441207,
    -0.00010707369659582431,
    -8.718649699740841e-05,
    -6.868206013150349e-05,
    -5.147111557410417e-05,
    -3.545835631958415e-05,
    -2.0543679706716553e-05,
    -6.623419998336781e-06,
    6.408434795357926e-06,
    1.8659049053200733e-05,
    3.0235575598202085e-05,
    4.1244028350383734e-05,
    5.178819794210575e-05,
    6.196861631458429e-05,
    7.18815759384496e-05,
    8.161820889789103e-05,
]

class FFTSpectrum:
    def __init__(self, f0: float = 0, df: float = 0, data=None):
        if data is None:
            data = []
        self.f0 = f0
        self.df = df
        self.data = data


class ComplexWaveform:
    def __init__(self, t0: float = 0, dt: float = 0, data=None):
        if data is None:
            data = []
        self.data = data
        self.dt = dt
        self.t0 = t0


class MAPro:
    @staticmethod
    def two_sided_fft_spectrum(signal_V, dt):
        fft_x = numpy.fft.fft(signal_V)
        n = math.floor(len(signal_V) / 2)

        fft_x = [x * (1 / len(signal_V)) for x in fft_x[:]]
        fft_x = numpy.roll(fft_x, int(len(fft_x) / 2))
        df = 1 / (dt * len(signal_V))
        f0 = df * (-n)

        return fft_x, df, f0

    @staticmethod
    def convert_to_db(spectrum):
        db_spectrum = [(float('-inf') if x == 0 else math.log10(x)) * 20 for x in spectrum]
        return db_spectrum

    @staticmethod
    def fft_spectrum_mag_phase(waveform: ComplexWaveform):
        # [x * y for x, y in zip(waveform.Y, numpy.hanning(len(waveform.Y)))]
        windowed_X = numpy.multiply(waveform.data, numpy.hanning(len(waveform.data)))
        waveform.data = windowed_X

        fft_x, df, f0 = MAPro.two_sided_fft_spectrum(waveform.data, waveform.dt)
        r_ar = []
        e_ar = []

        for z in fft_x:
            r, theta = cmath.polar(z)
            r_ar.append(r)
            e_ar.append(theta)

        mag_spectrum = MAPro.convert_to_db(r_ar)
        return FFTSpectrum(f0=f0, df=df, data=mag_spectrum)

    @staticmethod
    def extract_multiple_tone_information(time_signal_in: ComplexWaveform, export_mode, threshold, max_num_tones,
                                          output_sorting):
        spectrum, spectrum_length = MAPro.fft_with_hanning(time_signal_in.data)
        fs, f0, df, spectral_info = MAPro.get_waveform_info(time_signal_in)

        fft_spectrum = FFTSpectrum(f0, df, spectrum)

        criterion = {'single/multiple': 'Single Max Peak',
                     'threshold': threshold}

        three_fingers, l = MAPro.locate_three_fingers(fft_spectrum, spectral_info, criterion)

        t1 = []
        for tf in three_fingers:
            t1.append(MAPro.tone_estimation_fft_input_cdb(tf, spectral_info))

        t2 = []

        for i, tone_info in enumerate(t1[:]):
            freq = t1[i][0] * fs
            amp = t1[i][1] * math.sqrt(2)
            phase = t1[i][2] * 1
            t2.append((freq, amp, phase))

        t3 = []
        for tone_info in t2:
            if tone_info[1] > threshold:
                t3.append(tone_info)

        t3 = MAPro.sort_tone_info(t3)[::-1]
        t3 = t3[:max_num_tones]

        if output_sorting != 'increasing frequency':
            t3 = sorted(t3, key=lambda tone: tone[0])

        return t3

    @staticmethod
    def sort_tone_info(tone_info_ar):
        sorted_by_amplitude = sorted(tone_info_ar, key=lambda tone: tone[1])
        return sorted_by_amplitude

    @staticmethod
    def get_waveform_info(waveform):
        f0 = -1 * (math.floor(len(waveform.data) / 2) * (1 / (waveform.dt * len(waveform.data))))
        fs = 1 / waveform.dt
        df = 1 / (len(waveform.data) * waveform.dt)

        spectral_info = {'spectral_type': 'fft_spectrum[complex]', 'linear/db': 'linear', 'window': 'hanning',
                         'window_size': len(waveform.data), 'fft_size': len(waveform.data)}

        return fs, f0, df, spectral_info

    @staticmethod
    def fft_with_hanning(time_signal_data):
        fft_size = len(time_signal_data)

        x_mult = math.sqrt(2) / fft_size
        X = time_signal_data * x_mult #  [x * x_mult for x in time_signal_data]

        windowed_X = np.multiply(X, _calc_np_hanning_cache(fft_size)) #  [x * y for x, y in zip(X, _calc_np_hanning_cache(fft_size))]

        # complex_hann_spectrum = numpy.fft.fftshift(windowed_X)
        complex_hann_spectrum = numpy.fft.fftshift(numpy.fft.fft(windowed_X))

        return complex_hann_spectrum, fft_size

    @staticmethod
    def locate_three_fingers(fft_spectrum: FFTSpectrum, spectral_info, criterion):
        three_fingers = []
        if len(fft_spectrum.data) - 1 > 2:
            s = criterion.get('single/multiple') == 'Single Max Peak' and criterion.get('threshold') <= 0

            t2 = fft_spectrum.data[:-1]
            t1 = np.absolute(t2)

            max_t = (max(t1) * 0.10 if s else criterion.get('threshold')) / 1.6

            t1 = np.append(t1, 0.00)

            cont = True
            index = 0
            while cont:
                a = t1[index]
                b = t1[index + 1]
                c = t1[index + 2]
                if max_t > a and max_t > b and max_t > c:
                    pass
                else:
                    if (a >= b and a > c and index == 0) or (b >= a and b >= c):
                        new_fft_spectrum = FFTSpectrum(0.0, 0.0, [])
                        fft_size = spectral_info.get('fft_size')
                        df = fft_spectrum.df
                        f0 = fft_spectrum.f0

                        new_fft_spectrum.f0 = (1 / (fft_size * df) * df) * index + 1 / (fft_size * df) * f0

                        new_fft_spectrum.df = (1 / (
                                spectral_info.get('fft_size') * fft_spectrum.df) * fft_spectrum.df)

                        new_fft_spectrum.data = t2[index: index + 3]

                        three_fingers.append(new_fft_spectrum)
                        index += 1

                    else:
                        pass
                index += 1
                cont = index < len(fft_spectrum.data) - 2

            pass
        else:
            pass
        return three_fingers, len(three_fingers)

    @staticmethod
    def tone_estimation_fft_input_cdb(three_fingers: FFTSpectrum, spectral_info):
        initial_guess, range = MAPro.initial_guess_for_tone_estimation_fft_input(three_fingers, spectral_info)

        f0 = three_fingers.f0
        df = three_fingers.df * 2 + f0

        window = spectral_info.get('window')
        N = spectral_info.get('window_size')
        cont = True
        index = 0
        amplitude = 0
        phase = 0
        while cont:
            amplitude, phase, gf = MAPro.tone_estimation_subroutine_gf_fft_input_cdb(three_fingers, N, window,
                                                                                     frequency=initial_guess)
            gdf = MAPro.tone_estimation_subroutine_gf_first_derivative_fft_input_cdb(three_fingers, window, N,
                                                                                     frequency=initial_guess)

            initial_guess = initial_guess - (gf / gdf)

            x1 = not (df >= initial_guess >= f0)
            x2 = abs(gf / gdf) <= 1.00e-8
            x3 = index >= 100

            cont = not (x1 or x2 or x3)
            index += 1

        return initial_guess, amplitude, phase

    @staticmethod
    def initial_guess_for_tone_estimation_fft_input(three_fingers: FFTSpectrum, spectral_info):
        initial_guess, range = MAPro.initial_guess_for_tone_estimation_amplitude_input(
            FFTSpectrum(three_fingers.f0, three_fingers.df,
                        [abs(x) for x in
                         three_fingers.data]),
            spectral_info)

        return initial_guess, range

    @staticmethod
    def initial_guess_for_tone_estimation_amplitude_input(three_fingers: FFTSpectrum, spectral_info):

        gdf1 = MAPro.tone_estimation_subroutine_first_derivative(three_fingers,
                                                                 spectral_info.get('window'),
                                                                 spectral_info.get('window_size'),
                                                                 frequency=three_fingers.f0 + three_fingers.df * 1.00)

        gdf2 = MAPro.tone_estimation_subroutine_first_derivative(three_fingers,
                                                                 spectral_info.get('window'),
                                                                 spectral_info.get('window_size'),
                                                                 frequency=three_fingers.f0 + three_fingers.df * 0.75)

        f1 = three_fingers.f0 + three_fingers.df * 0.75 if gdf1 * gdf2 >= 0 else three_fingers.f0 + three_fingers.df * 1.00
        f2 = three_fingers.f0 + three_fingers.df * 1.00 if gdf1 * gdf2 >= 0 else three_fingers.f0 + three_fingers.df * 1.25

        _, gf1 = MAPro.tone_estimation_subroutine_gf_amplitude_input(three_fingers, f1, spectral_info.get('window'),
                                                                     spectral_info.get('window_size'))

        _, gf2 = MAPro.tone_estimation_subroutine_gf_amplitude_input(three_fingers, f2, spectral_info.get('window'),
                                                                     spectral_info.get('window_size'))

        x = ((gf1 / (gf1 - gf2)) * (f2 - f1)) + f1

        coherent_gain, enbw, _3db_bw, _6db_bw = MAPro.window_parameters(spectral_info.get('window'))

        m = (max(spectral_info.get('fft_size') / spectral_info.get('window_size') * _6db_bw, 2.0) / 2.0) * three_fingers.df
        upper_limit = three_fingers.df + three_fingers.f0 + m
        lower_limit = three_fingers.df + three_fingers.f0 - m

        range = (upper_limit, lower_limit)

        initial_guess = x if lower_limit < x < upper_limit else (upper_limit + lower_limit) / 2.0
        return initial_guess, range

    @staticmethod
    def window_frequency_response(window, f, N):
        if window == 'hanning':
            coefs = [0.5, 0.5]
        else:
            raise NotImplemented

        res = 0 + 0j
        sign = 1.0
        for index, c in enumerate(coefs):
            f1 = MAPro.rect_window_frequency_response(f - index / N, N)
            f2 = MAPro.rect_window_frequency_response(f + index / N, N)

            fsum = f1 + f2
            new_result = c * fsum * sign
            res = res + new_result
            sign *= -1

        Hf = res / (coefs[0] * 2.0)
        return Hf

    @staticmethod
    def rect_window_frequency_response(f, N):

        r = numpy.sinc(f * N) / numpy.sinc(f)
        theta = (f * numpy.pi) * (1.00 - N)

        Hf = complex(r * math.cos(theta), r * math.sin(theta))

        return Hf

    @staticmethod
    def window_frequency_response_array(f_ar, N, window):
        hf = []
        for f in f_ar:
            hf.append(MAPro.window_frequency_response(window, f, N))

        return hf

    @staticmethod
    def window_parameters(window):
        if window == 'hanning':
            enbw = 1.5
            coherent_gain = 0.5
            _6db_bw = 2.0
            _3db_bw = 1.44
            return coherent_gain, enbw, _3db_bw, _6db_bw
        raise NotImplemented

    @staticmethod
    def tone_estimation_subroutine_gf_amplitude_input(three_fingers: FFTSpectrum, frequency, window, N):
        f_normalized = []
        for i in range(3):
            f_normalized.append((three_fingers.f0 + three_fingers.df * i) - frequency)

        f = [abs(x) for x in MAPro.window_frequency_response_array(f_normalized, N, window)]
        a1 = []
        a2 = []

        for i in range(2):
            a1.append(sum(three_fingers.data[: len(three_fingers.data) - i]))
            a2.append(sum(f[: len(three_fingers.data) - i]))

        amplitude = a1[0] / a2[0]
        gf = abs(a1[0] * a2[1]) - abs(a1[1] * a2[0])

        return amplitude, gf

    @staticmethod
    def tone_estimation_subroutine_first_derivative(three_fingers: FFTSpectrum, window, N, frequency):
        ampl_x, x = MAPro.tone_estimation_subroutine_gf_amplitude_input(three_fingers, frequency + 2 ** (-30), window,
                                                                        N)
        ampl_y, y = MAPro.tone_estimation_subroutine_gf_amplitude_input(three_fingers, frequency - 2 ** (-30), window,
                                                                        N)

        gdf = (x - y) / (2 ** (-30) * 2)
        return gdf

    @staticmethod
    def tone_estimation_subroutine_gf_fft_input_cdb(three_fingers: FFTSpectrum, N, window, frequency):

        f_normalized = []
        for i in range(3):
            f_normalized.append((three_fingers.f0 + three_fingers.df * i) - frequency)

        f = MAPro.window_frequency_response_array(f_normalized, N, window)

        p = abs(three_fingers.f0 - frequency)
        m = abs(frequency - (2 * three_fingers.df + three_fingers.f0))
        n = three_fingers.df * 1.00e-6

        s = p <= n or m <= n

        spectrum_length = len(three_fingers.data)
        da1 = []
        da2 = []
        for i in range(2):
            a = three_fingers.data[:spectrum_length - i]
            b = f[:spectrum_length - i]
            dot1 = numpy.vdot(a, b)
            dot2 = numpy.vdot(b, b)
            da1.append(dot1)
            da2.append(dot2)

        x1 = da1[0]
        x2 = da1[1]

        y1 = da2[0]
        y2 = da2[1]

        gf = abs(x1 * y2) - abs(x2 * y1)

        if s:
            x1 = x1 / 2.0

        r, theta = cmath.polar(x1 / y1)

        amplitude = r

        phase = theta * (1 / math.pi) * 180.0

        return amplitude, -phase, gf

    @staticmethod
    def tone_estimation_subroutine_gf_first_derivative_fft_input_cdb(three_fingers: FFTSpectrum, window, N, frequency):
        amplitude1, phase1, gf1 = MAPro.tone_estimation_subroutine_gf_fft_input_cdb(three_fingers, N, window,
                                                                                    frequency + 2 ** (-30))
        amplitude2, phase2, gf2 = MAPro.tone_estimation_subroutine_gf_fft_input_cdb(three_fingers, N, window,
                                                                                    frequency - 2 ** (-30))
        gdf = (gf1 - gf2) / (2 * 2 ** (-30))
        return gdf


class PilotPowerCalculation:
    def __init__(self):
        self.fir = firwin(640, 0.02)

    def calc_power_freq(self, dl_rx_link_waveform, sample_rate=60_000_000, frequency=8_000_000):
        sine_ca = self.generate_sine_vectorize(len(dl_rx_link_waveform),
                                               sample_rate=sample_rate,
                                               frequency=frequency,
                                               offset=0)

        data2 = np.multiply(dl_rx_link_waveform, sine_ca)
        filtered2 = filtfilt(self.fir, [1.0], data2)
        freq2, amp2, phase2 = self.extract_multitone_information(filtered2, sample_rate)

        if amp2 > 0:
            pilot_power_dl = np.log10(amp2) * 20 + 10
        else:
            pilot_power_dl = 0

        return pilot_power_dl, freq2

    def calc(self, dl_rx_link_waveform, sample_rate=60_000_000, frequency=-9_500_000):
        n = len(dl_rx_link_waveform)
        ts = 1 / sample_rate
        shift = np.exp(2j * np.pi * -frequency * np.arange(0.0, ts * n, ts))
        filtered = signal.convolve(FIRWIN_5E5_256, dl_rx_link_waveform * shift)
        filtered_decimated_log = 10.0 * np.log10(np.abs(filtered) ** 2)

        return np.mean(filtered_decimated_log)

    # def calc(self, dl_rx_link_waveform, sample_rate=60_000_000, frequency=8_000_000):
    #     sine_ca = self.generate_sine_vectorize(len(dl_rx_link_waveform),
    #           sample_rate=sample_rate,
    #           frequency=frequency,
    #           offset=0)
    #
    #     data2 = np.multiply(dl_rx_link_waveform, sine_ca)
    #     filtered2 = filtfilt(self.fir, [1.0], data2)
    #     freq2, amp2, phase2 = self.extract_multitone_information(filtered2, sample_rate)
    #
    #     if amp2 > 0:
    #         pilot_power_dl = np.log10(amp2) * 20 + 10
    #     else:
    #         pilot_power_dl = 0
    #
    #     return pilot_power_dl

    @staticmethod
    @functools.cache
    def generate_sine_vectorize(n: int = 2028, sample_rate: int = 30_000_000, frequency: int = 10_200_000, offset: int = 0):
        sine1, sine2 = PilotPowerCalculation.generate_sine(n, sample_rate, frequency, offset)

        return np.vectorize(complex)(sine2, sine1)

    @staticmethod
    @functools.cache
    def generate_sine(n: int = 2028, sample_rate: int = 30_000_000, frequency: int = 10_200_000, offset: int = 0):
        phase1 = 0
        phase2 = 90
        amplitude = 1

        sine_1 = sine_wave(frequency=frequency,
                           samplerate=sample_rate, amplitude=amplitude, offset=offset, phaseshift=phase1,
                           length=n / sample_rate)
        sine_2 = sine_wave(frequency=frequency,
                           samplerate=sample_rate, amplitude=amplitude, offset=offset, phaseshift=phase2,
                           length=n / sample_rate)

        return sine_1, sine_2

    def extract_multitone_information(self, data, sample_rate):
        time_signal_in = ComplexWaveform(0, 1 / sample_rate, data)

        tone_info_arr = MAPro.extract_multiple_tone_information(time_signal_in, export_mode='none', threshold=0.001,
                                                                max_num_tones=1, output_sorting='increasing frequency')

        try:
            freq, amp, phase = tone_info_arr[0]
        except:
            amp = 0
            freq = 0
            phase = 0
        return freq, amp, phase



@functools.cache
def _calc_np_hanning_cache(fft_size: int):
    return numpy.hanning(fft_size)


